CoMarshalInterface vraci E_INVALIDARG
Otázka od: Karel Kimes
19. 9. 2002 16:40
ahoj,
prosim projdete si nize uvedeny kod metody COM komponenty.
procedure TKCH_DCOM_03.Go(delay: Integer);
var
_IStream : IStream;
_IStream_h : THandle;
res : HRESULT;
begin
res := CreateStreamOnHGlobal(0, false, _IStream);
if res <> S_OK then
raise Exception.Create('CreateStreamOnHGlobal error: ' + IntToStr(res));
res := GetHGlobalFromStream(_IStream, _IStream_h);
if res <> S_OK then
raise Exception.Create('GetHGlobalFromStream error: ' + IntToStr(res));
res := CoMarshalInterface(_IStream, DIID_IKCH_DCOM_03Events, FEvents,
MSHCTX_DIFFERENTMACHINE, nil, MSHLFLAGS_NORMAL);
// ***** pri pouziti komponenty na jednom PC je vse OK, ALE pokud se snazim
volat komponentu na jinem PC (DCOM je nastaveno dobre, vcetne
opravneni!!!), tak mne to vraci E_INVALIDARG. O co gou???
if res <> S_OK then
raise Exception.Create('CoMarshalInterface error: ' + IntToStr(res));
_IStream := nil;
// **** uvolnim timto _IStream??? nebo musim volat _Release???
TTestThread.Create(delay, _IStream_h); // ve vlakne provadim
CoUnmarshallInterface
CreateLOG('d:\dcom\_GoEnd.txt');
end;
kchodl
WinXP, D6PRO UPDATE PACK 2
Odpovedá: Malecek Ondrej
19. 9. 2002 16:35
Kde se vzalo FEvents ve volani CoMarshalInterface ??? Takhle z toho nejde
nic moc vykoukat
O
> -----Původní zpráva-----
> ahoj,
> prosim projdete si nize uvedeny kod metody COM komponenty.
>
>
>
> procedure TKCH_DCOM_03.Go(delay: Integer);
> var
> _IStream : IStream;
> _IStream_h : THandle;
> res : HRESULT;
> begin
> res := CreateStreamOnHGlobal(0, false, _IStream);
> if res <> S_OK then
> raise Exception.Create('CreateStreamOnHGlobal error: ' +
> IntToStr(res));
>
> res := GetHGlobalFromStream(_IStream, _IStream_h);
> if res <> S_OK then
> raise Exception.Create('GetHGlobalFromStream error: ' +
> IntToStr(res));
>
>
> res := CoMarshalInterface(_IStream, DIID_IKCH_DCOM_03Events, FEvents,
> MSHCTX_DIFFERENTMACHINE, nil, MSHLFLAGS_NORMAL);
> // ***** pri pouziti komponenty na jednom PC je vse OK, ALE pokud
> se snazim
> volat komponentu na jinem PC (DCOM je nastaveno dobre, vcetne
> opravneni!!!), tak mne to vraci E_INVALIDARG. O co gou???
>
>
>
> if res <> S_OK then
> raise Exception.Create('CoMarshalInterface error: ' + IntToStr(res));
>
> _IStream := nil;
> // **** uvolnim timto _IStream??? nebo musim volat _Release???
>
>
> TTestThread.Create(delay, _IStream_h); // ve vlakne provadim
> CoUnmarshallInterface
>
> CreateLOG('d:\dcom\_GoEnd.txt');
> end;
>
>
> kchodl
>
> WinXP, D6PRO UPDATE PACK 2
>
Odpovedá: Karel Kimes
23. 9. 2002 9:25
>Kde se vzalo FEvents ve volani CoMarshalInterface ???
FEvents je private atribut tridy TKCH_DCOM_03, ktery byl vytvoren pomoci
wizardu v D6 (na zaklade DAX) a obsahuje source interface, jehoz metody
musi klient implementovat v sink objektu. V tomto konkretnim pripade,
obsahuje, mimo jine i metodu OnEvent, kterou klient MUSI implementovat.
Tento interface se tedy snazim marshalovat. V pripade behu jako out-process
je vse OK. V pripade behu klienta a serveru na ruznych PC dojde ke zminene
chybe E_INVALIDARG pri volani CoMarshalInterface.
> > -----Původní zpráva-----
> > ahoj,
> > prosim projdete si nize uvedeny kod metody COM komponenty.
> >
> >
> >
> > procedure TKCH_DCOM_03.Go(delay: Integer);
> > var
> > _IStream : IStream;
> > _IStream_h : THandle;
> > res : HRESULT;
> > begin
> > res := CreateStreamOnHGlobal(0, false, _IStream);
> > if res <> S_OK then
> > raise Exception.Create('CreateStreamOnHGlobal error: ' +
> > IntToStr(res));
> >
> > res := GetHGlobalFromStream(_IStream, _IStream_h);
> > if res <> S_OK then
> > raise Exception.Create('GetHGlobalFromStream error: ' +
> > IntToStr(res));
> >
> >
> > res := CoMarshalInterface(_IStream, DIID_IKCH_DCOM_03Events, FEvents,
> > MSHCTX_DIFFERENTMACHINE, nil, MSHLFLAGS_NORMAL);
> > // ***** pri pouziti komponenty na jednom PC je vse OK, ALE pokud
> > se snazim
> > volat komponentu na jinem PC (DCOM je nastaveno dobre, vcetne
> > opravneni!!!), tak mne to vraci E_INVALIDARG. O co gou???
> >
> >
> >
> > if res <> S_OK then
> > raise Exception.Create('CoMarshalInterface error: ' + IntToStr(res));
> >
> > _IStream := nil;
> > // **** uvolnim timto _IStream??? nebo musim volat _Release???
> >
> >
> > TTestThread.Create(delay, _IStream_h); // ve vlakne provadim
> > CoUnmarshallInterface
> >
> > CreateLOG('d:\dcom\_GoEnd.txt');
> > end;
> >
> >
> > kchodl
> >
> > WinXP, D6PRO UPDATE PACK 2
> >
Odpovedá: Malecek Ondrej
23. 9. 2002 11:58
Hm, pokud mas ten interface vzity z TDispatch, neni nutno pri jeho predavani
do jineho
procesu marshallovat. Manualni marshalovani je nutne pouze pri predavani
mezi apartmenty uvnitr procesu. Mimo process se o to stara standardni
proxy-stub, akorat nutno pouzivat pouze automation compatible typy, ale to
asi vis.
Ale zpatky k tomu problemu. Nemuze to byt tim, ze na tech masinach nemas
registrovana vsechna pozuvana rozhrani. Mel jsem problemy s tim, ze pokud
jsem pouzival eventy mezi dvema PC, musel jsem mit rozhrani jak serveru, tak
i klienta registrovana na obou strojich, mam dojem, ze jsem nekde cetl o
souvislosti s tim, jak Delphi pracuji s typovymi knihovnami. Nemuze to byt
timhle ???
O
> -----Puvodni zprava-----
> >Kde se vzalo FEvents ve volani CoMarshalInterface ???
>
> FEvents je private atribut tridy TKCH_DCOM_03, ktery byl vytvoren pomoci
> wizardu v D6 (na zaklade DAX) a obsahuje source interface, jehoz metody
> musi klient implementovat v sink objektu. V tomto konkretnim pripade,
> obsahuje, mimo jine i metodu OnEvent, kterou klient MUSI implementovat.
> Tento interface se tedy snazim marshalovat. V pripade behu jako
> out-process
> je vse OK. V pripade behu klienta a serveru na ruznych PC dojde
> ke zminene
> chybe E_INVALIDARG pri volani CoMarshalInterface.
Odpovedá: Karel Kimes
23. 9. 2002 16:45
>Hm, pokud mas ten interface vzity z TDispatch, neni nutno pri jeho predavani
>do jineho
>procesu marshallovat. Manualni marshalovani je nutne pouze pri predavani
>mezi apartmenty uvnitr procesu.
**** mas pravdu, uz sem to objevil (pozde, ale prece - jedna se o MTA =>
no marshaling required)
>akorat nutno pouzivat pouze automation compatible typy, ale to
>asi vis.
**** vim
>Ale zpatky k tomu problemu. Nemuze to byt tim, ze na tech masinach nemas
>registrovana vsechna pozuvana rozhrani. Mel jsem problemy s tim, ze pokud
>jsem pouzival eventy mezi dvema PC, musel jsem mit rozhrani jak serveru, tak
>i klienta registrovana na obou strojich, mam dojem, ze jsem nekde cetl o
>souvislosti s tim, jak Delphi pracuji s typovymi knihovnami. Nemuze to byt
>timhle ???
**** ZPATKY NA STROMY (zapomenme na to, ze sem resil marshalovani, protoze
uz vim kde je chyba, ALE neznam PRICINU)!!! Viz. dale...
1) Mam Automation objekt, ktery byl generovan pres wizarda v D6PRO, s
podporou events.
2) Mam klienta, ktery si naimportoval typovou knihovnu Automation objektu,
jako komponentu.
3) klient vola metodu napr. Go (dojde ke spojeni s Automation objektem)
4) automation objekt v implementaci metody Go ma vyvolat udalost. Tedy
napr. FEvents.OnEvent (FEvent je soucasti CoClass objektu a generoval ji
wizard, je to source interface, ktery MUSI implementovat klient)
Definujme varianty:
A) klient je spusten na PC, kde take registrovan automation objekt.
B) klient je spusten na PC, kde je registrovan automation objekt jako
remote server a automation objekt bezi na tomto remote serveru, kde je take
registrovan. (pro nastaveni DCOM sem pouzil DCOMCNFG.EXE a na obou PC sem
nastavil pro tento vsechna prava atd. => v tom bych problem nehledal, ale
na 100% si jistej nejsu )
U var. A je vse OK.
U var. B sem zjistil, ze je FEvents = NIL !!!
Mj. to je ten problem proc ten marshaling nefungoval (pokousel sem se
marshalovat interface, ktery byl NIL - coz asi nejde, ze ).
No dobre, ale jak z toho ven. Pokracoval sem v pruzkumu a zameril sem se na
klienta, ktery by v urcitem bode mel serveru predat ukazatel na sink event
objekt a to procedurou InterfaceConnect, definovanou v jednotce ComObj.pas.
Ta procedura vypada takto:
procedure InterfaceConnect(const Source: IUnknown; const IID: TIID;
const Sink: IUnknown; var Connection: Longint);
var
CPC: IConnectionPointContainer;
CP: IConnectionPoint;
begin
Connection := 0;
if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
CP.Advise(Sink, Connection);
end;
cely kod probehne spravne a do mista, kde se vola CP.Advise(Sink,
Connection), kde je Connection je cookie, ktery mam ziskat od Automation
objektu. U var. A je vse OK a cookie je 1, u var. B dostanu jako cookie 0.
Probuh, co ma kde nastavit, opravit, prepsat, upravit, ...., aby to
fungovalo. Su z toho jelen.
WinXP, D6PRO UPDATE PACK 2
kchodl
Odpovedá: Malecek Ondrej
24. 9. 2002 10:41
Abych pravdu rekl, framework okolo ConnectionPoints jsem zavrhl. Duvod:
univerzalni, ale bohuzel dost nepruhledne => neco na tom odladit chce pevne
nervy. Dale pri provozu po siti v tomto modelu naskakuji nezanedbatelne casy
pri volani metod jednotlivych callback rozhrani. Nakonec jsem skoncil u
toho, ze si velmi jednoduchou obsluhu realizuju sam, s tim, ze volani
kazdeho registrovaneho callback interfacu (Sink) mam delane ze specialniho
vlakna. Taky to ma samozrejme svoje => nelze pouzit pro vetsi mnozstvi
klientu (radove max. desitky), ale to mi staci. Vyhodou je, ze to ma clovek
docela pod kontrolou a volani callback interfacu mi bezi "paralelne", coz je
pro me dost podstatne.
O
> -----Puvodni zprava-----
> No dobre, ale jak z toho ven. Pokracoval sem v pruzkumu a zameril
> sem se na
> klienta, ktery by v urcitem bode mel serveru predat ukazatel na
> sink event
> objekt a to procedurou InterfaceConnect, definovanou v jednotce
> ComObj.pas.
> Ta procedura vypada takto:
>
>
> procedure InterfaceConnect(const Source: IUnknown; const IID: TIID;
> const Sink: IUnknown; var Connection: Longint);
> var
> CPC: IConnectionPointContainer;
> CP: IConnectionPoint;
> begin
> Connection := 0;
> if Succeeded(Source.QueryInterface(IConnectionPointContainer,
> CPC)) then
> if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
> CP.Advise(Sink, Connection);
> end;
>
>
> cely kod probehne spravne a do mista, kde se vola CP.Advise(Sink,
> Connection), kde je Connection je cookie, ktery mam ziskat od Automation
> objektu. U var. A je vse OK a cookie je 1, u var. B dostanu jako cookie 0.
>
> Probuh, co ma kde nastavit, opravit, prepsat, upravit, ...., aby to
> fungovalo. Su z toho jelen.
>
>
> WinXP, D6PRO UPDATE PACK 2
>
> kchodl
>
Odpovedá: Richard Kejval
27. 9. 2002 7:03
> Abych pravdu rekl, framework okolo ConnectionPoints jsem zavrhl. Duvod:
> univerzalni, ale bohuzel dost nepruhledne => neco na tom odladit chce
pevne
> nervy. Dale pri provozu po siti v tomto modelu naskakuji nezanedbatelne
casy
> pri volani metod jednotlivych callback rozhrani. Nakonec jsem skoncil u
> toho, ze si velmi jednoduchou obsluhu realizuju sam, s tim, ze volani
> kazdeho registrovaneho callback interfacu (Sink) mam delane ze specialniho
> vlakna. Taky to ma samozrejme svoje => nelze pouzit pro vetsi mnozstvi
> klientu (radove max. desitky), ale to mi staci. Vyhodou je, ze to ma
clovek
> docela pod kontrolou a volani callback interfacu mi bezi "paralelne", coz
je
> pro me dost podstatne.
S ConnectionPoints mam take dost spatne zkusenosti, nemohl bys to upresnit
na nejakem kratickem prikladku nebo mi to poslat na soukromy mail
kejvalr@volny.cz
Diky za ochotu
S pozdravem
Richard Kejval
Odpovedá: Malecek Ondrej
27. 9. 2002 12:57
No, abych byl presny, puvodne se tu mluvilo o ConnectionPoints pri pouziti
pres DCOM. V takovem tom "klasickem" pojeti (napr. event system, ktery ti
generuje wizard Delphi pro ActiveX komponentu) asi ConnectionPoints vyhovi.
Tu drive zminovanou obdobu pouzivam tedy v pripadech, kdy se mi
ConnectionPoints zdaji "ne uplne vhodne".
Napr:
TClientCallbackItem = class( TThread)
m_Callback : IClientCallback;
procedure MakeCallbackCall;
Procedure Execute;
end;
TServer = classs( ..., IServer)
private
procedure NotifyCallbacks;
m_CallbackList : TList;
public
procedure RegisterCallback( IClientCallback);
end;
procedure TServer.RegisterCallback( extCallback);
var m_Callback : TClientCallbackItem;
begin
m_Callback := TClientCallbackItem.Create( extCallback);
end;
procedure TServer.NotifyCallbacks;
var intI;
begin
for intI := 0 to ( m_CallbackList.Items.Count - 1) do
begin
TClientCallbackItem( m_CallbackList.Items[i]).MakeCallbackCall;
end;
end;
procedure TClientCallbackItem.MakeCallbackCall;
begin
PostThreadMessage( ThreadID, MSG_MAKE_CALLBACK_CALL, 0, 0);
end;
procedure TClientCallbackItem.Execute
begin
while GetMessage( msg) do
begin
case msg.message of
MSG_MAKE_CALLBACK_CALL:
begin
m_Callback.Notify;
end;
end;
end;
end;
Vyse uvedene nutno brat pouze jako prikladek (urcite to nepujde prelozit
. Pri volani
IServer.RegisterCallback se preda rozhrani, ktere ma server volat pri vzniku
udalosti (samozrejme je potreba uvnitr volat QueryInterface a takove ty
opicarny) Vytvori se instance TThread (TClientCallbackItem), ktera bude
obsluhovat volani (pozor na marshalling mezi apartmentem, kde se instance
vytvari a apartmentem toho threadu, ktery pak bude obsluhovat volani) a
zaradi se do seznamu registrovanych callbacku. Jak se provadi volani
callbacku pri vzniku udalosti je uz asi zrejme. Vlastni volani metod
IClientCallback je pak provadeno vzdy v k tomu ucelu vytvorenemu threadu =>
zejmena na siti se jednotliva volani mezi sebou neovlivnuji.
O
> -----Původní zpráva-----
> S ConnectionPoints mam take dost spatne zkusenosti, nemohl bys to upresnit
> na nejakem kratickem prikladku